package QianFeng16.XML;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.Xpp3DomDriver;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.*;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * XML指可扩展标记语言（Extensible Markup Language）
 * XML是一个以文本来描述数据的文档
 * XML技术的用途：
 * 1、充当显示数据（以XML充当显示层）
 * 2、存储数据（存储层）的功能
 * 3、以XML描述数据，并在联系服务器与系统的其余部分之间传递（传输数据的一样格式）
 * XML是一种通用的数据交换格式,它的平台无关性、语言无关性、系统无关性、给数据集成与交互带来了极大的方便。
 * XML在不同的语言环境中解析方式都是一样的,只不过实现的语法不同而已。
 *
 * XML的四中解析：
 * 学习链接：https://www.cnblogs.com/longqingyang/p/5577937.html
 * XML的解析方式分为四种：1、DOM解析；2、SAX解析；3、JDOM解析；4、DOM4J解析。
 * 其中前两种属于基础方法，是官方提供的平台无关的解析方式；
 * 后两种属于扩展方法，它们是在基础的方法上扩展出来的，只适用于java平台。
 **/
public class XMLDemo {

    /**
     * SAX的全称是Simple APIs for XML，也即XML简单应用程序接口。
     * SAX是读取和操作XML数据更快、更轻量的方法。
     * 与DOM不同，SAX提供的访问模式是一种顺序模式，这是一种快速读写XML数据的方式。
     * 当使用SAX分析器对XML文档进行分析时，会触发一系列事件，并激活相应的事件处理函数，
     * 应用程序通过这些事件处理函数实现对XML文档的访问，因而SAX接口也被称作事件驱动接口。
     * SAX API是一个基于事件的API，适用于处理数据流，即随着数据的流动而依次处理数据。
     * SAX API在其解析您的文档时发生一定事件时候会通知您。在您对其响应时，您不作保存的数据将会被拋弃。
     * 优点：
     * 1、采用事件驱动模式，对内存耗费比较小。
     * 2、适用于只处理XML文件中的数据时。
     * 3、顺序读取，速度快
     * 缺点：
     * 1、编码比较麻烦。
     * 2、很难同时访问XML文件中的多处不同数据。
     * 3、不能任意读取节点（灵活性差）
     * 总结：SAX更适合用于性能要求更高的设备上使用（Android开发中）
     * SAX解析XML：
     * SAX API中主要有四种处理事件接口，分别是ContentHandler，DTDHandler，EntityResolver和ErrorHandler。
     * 实际上只要继承DefaultHandler类就可以，DefaultHandler实现了这四个事件处理器接口，然后提供了每个抽象方法的默认实现。
     * //创建SAX解析器工厂对象
     * SAXParserFactory spf = SAXParserFactory.newInstance();
     * //使用解析器工厂创建解析器实例
     * SAXParser saxParser = spf.newSAXParser(;
     * //创建SAX解析器要使用的事件侦听器对象
     * PersonHandler handler = new PersonHandler();
     * //开始解析文件
     * saxParser.parse(new File(fileName), handler);
     **/
    @Test
    public void asxParseXML() throws ParserConfigurationException, SAXException, IOException {
        //1、创建一个SAX解析器工厂对象
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        //2、通过工厂对象创建SAX解析器
        SAXParser saxParser = saxParserFactory.newSAXParser();
        //3、创建一个数据处理器（需要我们自己编写）
        PersonHandler personHandler = new PersonHandler();
        //4、开始解析
        InputStream inputStream = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("QianFeng16/XML/person.xml");
        assert inputStream != null;
        saxParser.parse(inputStream, personHandler);
        //将解析后的数据放入数组中
        List<Person> persons = personHandler.getPersons();
        //遍历数组
        for (Person person : persons) {
            System.out.println(person);
        }
    }
//---------------------------------------------------------------------------------------------------------------------
    /**
     * DOM（Document Object Model 文档对象模型）定义了访问和操作文档的标准方法。
     * 基于DOM解析的xml分析器是将其转换为一个对象模型的集合，用树这种数据结构对信息进行储存。
     * 通过DOM接口，应用程序可以在任何时候访问xml文档中的任何一部分数据，
     * 因此这种利用DOM接口访问的方式也被称为随机访问。
     * DOM的特性: .
     * 定义一组Java接口，基于对象，与语言和平台无关将XML文档表示为树，
     * 在内存中解析和存储XML文档，允许随机访间文档的不同部分。
     * ----------------------------------------------------------------------------------
     * DOM的优点：
     * 由于树在内存中是持久的，因此可以修改后更新。
     * 它还可以在任何时候在树中上下导航，API使用起来也较简单。
     * DOM的缺点：
     * 因为DOM分析器将整个xml文件转换为了树存放在内存中，
     * 当文件结构较大或者数据较复杂的时候，这种方式对内存的要求就比较高，
     * 且对于结构复杂的树进行遍历也是一种非常耗时的操作。
     * 总结：基于树形结构，通过解析器一次性把文档加载到内存中，所以会较占内存，
     *       可以随机访问，更加灵活，更适合在Web开发中使用
     * -----------------------------------------------------------------------------------
     * DOM解析XML：
     * DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
     * DocumentBuilder db = builder.newDocumentBuilder();
     * db.parse("person.xml");
     * NodeList node_ person = doc.getElementsByTagName("person");
     **/
    @Test
    public void domParseXML() throws ParserConfigurationException, IOException, SAXException {
        //1、创建一个DOM解析器工厂对象
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //2、通过工厂对象创建一个解析器对象
        DocumentBuilder documentBuilder = factory.newDocumentBuilder();
        //3、解析文件
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("QianFeng16/XML/person.xml");
        assert is != null;
        //此代码完成后，整个XML文档已经被加载到内存中，以树状形式存储
        Document doc = documentBuilder.parse(is);
        //4、从内存中读取数据
        //获取节点名称为person的所有节点，返回节点集合
        NodeList personNodeList = doc.getElementsByTagName("person");
        ArrayList<Person> persons = new ArrayList<>();
        Person p = null;
        //此循环会迭代两次
        for (int i = 0; i < personNodeList.getLength(); i++) {
            Node personNode = personNodeList.item(i);
            p = new Person();
            //获取节点的属性值
            String personid = personNode.getAttributes()
                    .getNamedItem("personid").getNodeValue();
            p.setPersonid(personid);
            //获取当前节点的所有子节点
            NodeList childNodes = personNode.getChildNodes();
            for (int j = 0; j < childNodes.getLength(); j++) {
                Node item = childNodes.item(j);
                String nodeName = item.getNodeName();
                if ("name".equals(nodeName)) {
                    p.setName(item.getFirstChild().getNodeValue());
                } else if ("address".equals(nodeName)) {
                    p.setAddress(item.getFirstChild().getNodeValue());
                } else if ("tel".equals(nodeName)) {
                    p.setTel(item.getFirstChild().getNodeValue());
                } else if ("fax".equals(nodeName)) {
                    p.setFax(item.getFirstChild().getNodeValue());
                } else if ("email".equals(nodeName)) {
                    p.setEmail(item.getFirstChild().getNodeValue());
                }
            }
            persons.add(p);
        }
        System.out.println("结果：");
        for (Person Person : persons) {
            System.out.println(Person.toString());
        }
    }
//--------------------------------------------------------------------------------------------------------------------
    /**
     * JDOM是一个开源项目，它基于树形结构，利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。
     * 1、与DOM类似，基于树形结构
     * 2、与DOM区别：
     *   （1）第三方开源组件
     *   （2）实现使用JAVA的Collection接口
     *   （3）效率比DOM更快
     * 使用场景：
     *  1、当需要知道整个文档结构
     *  2、使用比较简单，比JDK自带的Document解析性能稍好
     *  3、解析的树形结构操作比较灵活，比较简便
     * JDOM操作xml常用类：
     *  Document：表示整个xml文档，是一个树形结构
     *  Element：表示一个xml的元素，提供方法操作其子元素，如文本，属性和名称空间等
     *  Attribute：表示元素包含的属性
     *  Text：表示xml文本信息
     *  XMLOutputter：xml输出流，底层是通过JDK中流实现
     *  Format：提供xml文件输出的编码、样式和排版等设置
     *              实现步骤：
     *  第一步：创建一个sax解析器
     *  SAXBuilder builder = new SAXBuilder();
     *  第二步：根据xml结构转换成一个Document对象
     *  Document doc= builder.build(file);
     *  第三步：解析Document中的结点信息，把xml树形结构打印到控制台
     **/
    @Test
    public void jdomParseXML() throws JDOMException, IOException {
        //创建JDOM解析器
        SAXBuilder builder = new SAXBuilder();
        //解析文件
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("QianFeng16/XML/person.xml");
        //此代码完成后，整个XML文档已经被加载到内存中，以树状形式存储
        org.jdom2.Document build = builder.build(is);
        //获取根节点
        Element rootElement = build.getRootElement();
        //创建存储person对象的集合
        List<Person> persons = new ArrayList<>();
        Person person = null;
        //获取people子节点person，将person两个节点传给children集合
        List<Element> children = rootElement.getChildren();
        for (Element element : children) {//得到每个person
            person = new Person();
            //得到person的属性值
            String personid = element.getAttributeValue("personid");
            person.setPersonid(personid);
            //获取person子节点name、address等放入集合中
            List<Element> children1 = element.getChildren();
            for (Element element1 : children1) {
                String tag = element1.getName();//获取标签的名称
                if ("name".equals(tag)) {
                    person.setName(element1.getText());//获取name标签包裹着的值
                } else if ("address".equals(tag)) {
                    person.setAddress(element1.getText());
                } else if ("tel".equals(tag)) {
                    person.setTel(element1.getText());
                } else if ("fax".equals(tag)) {
                    person.setFax(element1.getText());
                } else if ("email".equals(tag)) {
                    person.setEmail(element1.getText());
                }
            }
            persons.add(person);
        }
        System.out.println("结果：");
        System.out.println(persons.toString());

    }
//-------------------------------------------------------------------------------------------------------------------
    /**
     * DOM4J
     * dom4j是一个非常非常优秀的Java XML API，具有性能优异、功能强大和极端易用使用的特点，
     * 同时它也是一个开放源代码的软件，可以在SourceForge上找到它。在对主流的Java
     * XML API进行的性能、功能和易用性的评测，这是必须使用的jar包，Hibernate用它来读写配置文件。
     * 学习链接：https://blog.csdn.net/redarmy_chen/article/details/12969219
     *
     * 1、基于树形结构，第三方组件
     * 2、解析速度快，效率高，使用的JAVA中的迭代器实现数据读取（Hibernate框架使用多）
     **/
    @Test
    public void dom4jParseXML() throws DocumentException {
        //1、创建DOM4J的解析器对象
        SAXReader reader = new SAXReader();
        //2、解析文档
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("QianFeng16/XML/person.xml");
        //通过read方法读取一个文件，转换成Document对象
        org.dom4j.Document doc = reader.read(is);
        //获取根节点元素对象
        org.dom4j.Element rootElement = doc.getRootElement();
        //构建迭代器来遍历所有的元素节点
        Iterator<org.dom4j.Element> iterator = rootElement.elementIterator();
        ArrayList<Person> persons = new ArrayList<>();
        Person p = null;
        //person的循环
        while (iterator.hasNext()) {
            p = new Person();
            org.dom4j.Element element = iterator.next();//直接子节点
            p.setPersonid(element.attributeValue("personid"));//获取personid属性值
            Iterator<org.dom4j.Element> iterator1 = element.elementIterator();
            //遍历获取person下的元素
            while (iterator1.hasNext()) {
                org.dom4j.Element element1 = iterator1.next();
                String tag = element1.getName();
                if ("name".equals(tag)) {
                    p.setName(element1.getText());//获取name标签包裹着的值
                } else if ("address".equals(tag)) {
                    p.setAddress(element1.getText());
                } else if ("tel".equals(tag)) {
                    p.setTel(element1.getText());
                } else if ("fax".equals(tag)) {
                    p.setFax(element1.getText());
                } else if ("email".equals(tag)) {
                    p.setEmail(element1.getText());
                }
            }
            persons.add(p);
        }
        System.out.println("结果：");
        System.out.println(persons.toString());
    }

//---------------------------------------------------------------------------------------------------------------------

    /*xml文件与对象之间的转换*/
    /**
     * 把对象转成XML文件写入
     **/
    @Test
    public void xmlEnCoder() throws FileNotFoundException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/QianFeng16/XML/test.xml"));
        XMLEncoder xmlEncoder = new XMLEncoder(bos);
        Person p = new Person();
        p.setPersonid("1212");
        p.setAddress("北京");
        p.setEmail("sun@163.com");
        p.setFax("67789548456");
        p.setTel("13814381438");
        p.setName("38");
        xmlEncoder.writeObject(p);
        xmlEncoder.close();
    }
    /**
     * 从xml文件中读取对象
     **/
    @Test
    public void xmlDeCoder() throws FileNotFoundException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/QianFeng16/XML/test.xml"));
        XMLDecoder xmlDecoder = new XMLDecoder(bis);
        Person p = (Person) xmlDecoder.readObject();
        System.out.println(p);
    }

    /**
     * 利用xStream组件来完成XML的解析与生成
     * xStream组件可以快速的在xml文件与对象之间转换
     * 对于用于xml作为信息传输格式来说，尤其方便
    **/
    @Test
    public void xStream() {
        //xml文件的生成
        Person p = new Person();
        p.setPersonid("1212");
        p.setAddress("北京");
        p.setEmail("sun@163.com");
        p.setFax("67789548456");
        p.setTel("13814381438");
        p.setName("38");
        XStream xStream = new XStream(new Xpp3DomDriver());
        xStream.alias("person",Person.class);//取一个别名
        xStream.useAttributeFor(Person.class, "personid");//将personid放置到属性中
        String xml = xStream.toXML(p);
        System.out.println(xml);
        //xml解析
        Person person = (Person) xStream.fromXML(xml);
        System.out.println(person);
    }
}
